#include "PinQTask.h"
#include <s32strm.h> // RWriteStream & RReadStream
//#include <txtetext.h> // CPlainText
#include "PinQ.hrh"

const TInt KTimeSecMultiple=1000000;

CPinQTask::CPref::CPref()
	{
	Reset();
	}

CPinQTask::CPref::~CPref()
	{
	delete iName;
	delete iNote;
	}

void CPinQTask::CPref::Reset()
	{
	delete iName;
	iName=NULL;
	delete iNote;
	iNote=NULL;
	iPktCount = PINQDEFPKTCOUNT;
	iPktSize = PINQDEFPKTSIZE;
	iDelay = PINQDEFDELAY;
	iTimeout = PINQDEFTIMEOUT;
	iLoop = true;
	iEndless = true;
	iCategory = EPinQCategoryUnfiled;
	}

void CPinQTask::CPref::SetNoteL(const TDesC& aText)
	{
	HBufC* temp = aText.AllocL();
	delete iNote;
	iNote = temp;
	}

void CPinQTask::CPref::SetNameL(const TDesC& aName)
	{
	HBufC* temp = aName.AllocL();
	delete iName;
	iName = temp;
	}

void CPinQTask::CPref::ExternalizeL(RWriteStream& aStream) const
	{
	aStream.WriteInt8L(PINQTASKPREFVER);
	aStream.WriteInt32L(iDelay);
	aStream.WriteInt32L(iTimeout);
	aStream.WriteInt32L(iPktSize);
	aStream.WriteInt32L(iPktCount);
	aStream.WriteInt8L(iEndless);
	aStream.WriteInt8L(iLoop);
	aStream.WriteInt32L(iCategory);
	if (iName)
		{
		aStream.WriteInt32L(iName->Length());
		aStream << (*iName);
		}
	else
		{
		aStream.WriteInt32L(KErrNotFound);
		}
	if (iNote)
		{
		aStream.WriteInt32L(iNote->Length());
		aStream << (*iNote);
		}
	else
		{
		aStream.WriteInt32L(KErrNotFound);
		}
	}

void CPinQTask::CPref::InternalizeL(RReadStream& aStream)
	{
	TInt ver = aStream.ReadInt8L();
	iDelay = aStream.ReadInt32L();
	iTimeout = aStream.ReadInt32L();
	iPktSize = aStream.ReadInt32L();
	iPktCount = aStream.ReadInt32L();
	iEndless = aStream.ReadInt8L();
	iLoop = aStream.ReadInt8L();
	iCategory = aStream.ReadInt32L();
	TInt valueLength = aStream.ReadInt32L();
	if (valueLength != KErrNotFound)
		{
		if (!iName)
			{
			iName = HBufC::NewL(valueLength);
			}
		else
			if (valueLength < iName->Length() )
				{
				iName->ReAlloc(valueLength);
				}

		TPtr ptr(iName->Des());
		aStream >> ptr;
		}
	valueLength = aStream.ReadInt32L();
	if (valueLength != KErrNotFound)
		{
		if (!iNote)
			{
			iNote = HBufC::NewL(valueLength);
			}
		else
			if (valueLength < iNote->Length() )
				{
				iNote->ReAlloc(valueLength);
				}
		TPtr ptr(iNote->Des());
		aStream >> ptr;
		}
	}

CPinQTask::CTimeout::CTimeout(CPinQTask& aTask) : CActive(CActive::EPriorityLow),iTask(aTask)
	{
	CActiveScheduler::Add(this); 
	}
CPinQTask::CTimeout::~CTimeout()
	{
	iTimer.Close();
	if (IsAdded() )
		Deque(); // calls also Cancel()
	}

void CPinQTask::CTimeout::ConstructL()
	{
	User::LeaveIfError(iTimer.CreateLocal());
	}
	
void CPinQTask::CTimeout::RunL()
	{
	iTask.OnAlarm(); 
	}

void CPinQTask::CTimeout::DoCancel()
	{
		iTimer.Cancel();
	}

void CPinQTask::CTimeout::Start()
	{
	TRequestStatus s=iStatus;
	TRequestStatus s1=iTask.iStatus;
	iTimer.After(iStatus,iTask.Pref().Timeout()*KTimeSecMultiple);
	ASSERT(!IsActive());
	SetActive();
	}

TInt CPinQTask::CTimeout::RunError(TInt aError)
	{
	Cancel();
	return aError;
	}

CPinQTask* CPinQTask::NewL()
	{
	CPinQTask* self = CPinQTask::NewLC();
	CleanupStack::Pop(self);
	return self;
	}

CPinQTask* CPinQTask::NewLC()
	{
	CPinQTask* self = new (ELeave) CPinQTask;
	CleanupStack::PushL(self);
	self->ConstructL();
	return self;
	}

CPinQTask* CPinQTask::NewL(const CPref& aPref)
	{
	CPinQTask* self = CPinQTask::NewLC(aPref);
	CleanupStack::Pop(self);
	return self;
	}

CPinQTask* CPinQTask::NewLC(const CPinQTask::CPref& aPref)
	{
	CPinQTask* self = new (ELeave) CPinQTask();
	CleanupStack::PushL(self);
	self->ConstructL(aPref);
	return self;
	}

CPinQTask::CPinQTask() :
	CActive(CActive::EPriorityStandard), iTimeout(*this)
	{
	iState = EStopped;
	CActiveScheduler::Add(this); // add to AS queue
	}

void CPinQTask::ConstructL()
	{
	//TODO: handle error?
	User::LeaveIfError(iSocketServ.Connect());
	User::LeaveIfError(iTimer.CreateLocal());
	iTimeout.ConstructL();
	}

void CPinQTask::ConstructL(const CPinQTask::CPref& aPref)
	{
	ConstructL();
	iPref.SetNameL(aPref.Name());
	iPref.SetNoteL(aPref.Note());
	iPref.SetCategory(aPref.Category());
	iPref.SetPktSize(aPref.PktSize());
	iPref.SetPktCount(aPref.PktCount());
	iPref.SetDelay(aPref.Delay());
	iPref.SetTimeout(aPref.Timeout());
	iPref.SetEndless(aPref.Endless());
	iPref.SetLoop(aPref.Loop());
	}

CPinQTask::~CPinQTask()
	{
	if (IsAdded() )
		Deque(); // calls also Cancel()
	iSocketServ.Close();
	iResolver.Close();
	iTimer.Close();
	DeletePackets();
	}

void CPinQTask::Start(MObserver& aObserver)
	{
	iSeq=0;
	iError=ENoError;
	iLeaveCode=0;
	DeletePackets();
	iObserver=&aObserver;
	ASSERT(iCount==0);
	OpenL();
	}
void CPinQTask::SetState(TState aState)
	{
	iObserver->TaskStateChangedL(*this, aState);
	iState=aState;
	}

const TDesC& CPinQTask::StateName(CPinQTask::TState aState)
	{
	_LIT(KNone,     "inactive ");
	_LIT(KLookingUp,"dns req  ");
	_LIT(KOpenning, "opening  ");
	_LIT(KDealying, "delaying ");
	_LIT(KSending,  "sending  ");
	_LIT(KSent,     "sent     ");
	_LIT(KReceiving,"receiving");
	_LIT(KRecvd,    "received ");
	_LIT(KNA,"N/A");
	switch (aState)
		{
		case EStopped:
			return KNone;
		case ELookingUp:
			return KLookingUp;
		case EOpenning:
			return KOpenning;
		case ESending:
			return KSending;
		case ESent:
			return KSent;
		case EReceiving:
			return KReceiving;
		case EReceived:
			return KRecvd;
		case EDelay:
			return KDealying;
		default:
			return KNA;
		}
	}

void CPinQTask::DoCancel()
	{
	switch(State())
		{
		case EDelay:
			iTimer.Cancel();
			break;
		case ESending:
			if (iError!=ETimeout) 
				iTimeout.Cancel();
			iSocket.CancelSend();
			break;
		case EReceiving:
			if (iError!=ETimeout) 
				iTimeout.Cancel();
			iSocket.CancelRecv();
			break;
		case ELookingUp:
			if (iError!=ETimeout)
				iTimeout.Cancel();
			iResolver.Cancel();
			iResolver.Close();
			break;
		}	
	}

void CPinQTask::RunL()
	{
	iTimeout.Cancel();
	switch (iState)
		{
		case ELookingUp:
			iResolver.Close();
			if (iStatus == KErrNone)
				{
				// DNS look up successful
				iNameRecord = iNameEntry();

				// And connect to the IP address
				OpenL(TInetAddr::Cast( iNameRecord.iAddr ).Address() );
				}
			else
				{
				//TODO: Handle error
				}
			break;
		case EDelay:
		case EOpenning:
			SendTo();
			break;
		case ESending:
			OnSent();
			break;
		case EReceiving:
			OnRecvd();
			break;
		default:
			ASSERT(0);
		}
	}

void CPinQTask::OnAlarm()
	{
	iError=ETimeout;
	
	if (IsActive())
		Cancel();
	switch(State())
		{
		case ELookingUp:
			SetState(EStopped);
			break;
		case EReceiving:
		case ESending:
			CPinQTask::TPacket* packet=LastPacket();
			ASSERT(packet);
			packet->iError=ETimeout;
			NextPing();
		}
	}

void CPinQTask::SendTo()
	{
	ASSERT(!IsActive());
	iSeq++;
	ASSERT(Pref().Endless() || Pref().Loop() || iSeq<=Pref().PktCount());
	iData[6]=iSeq>>8; // sequence number
	iData[7]=iSeq&0x00FF;
	iError=ENoError;
	
	TPacket* packet=AddPacket(iSeq,0);
	if (packet)
		{
		SetState(ESending);
		iSocket.SendTo(iData, iAddress, 0, iStatus);
		SetActive();
		iTimeout.Start();
		}else{
		//TODO: hanlde no mem
		}
	}

void CPinQTask::DeletePackets()
	{
		TPacket* p=iFirst;
		while (p){
			TPacket* pN=p->iNext;
			delete p;
			p=pN;
		}
		iFirst=iLast=iPrevious=NULL;
		iCount=0;
	}

CPinQTask::TPacket* CPinQTask::AddPacket(TUint aSeq,TUint aTime)
	{
		TPacket* p=new TPacket;
		if (p){
			if (!iPref.Endless())// qty is limiited
				{
				if (iPref.PktCount()==iCount) //qty hit limit
					{
					if (iPref.Loop())
						{
						//remove the very first
						TPacket* first=iFirst;
						if (first)
							{
							iFirst=iFirst->iNext;// move pointer of the first to the second
							if (first==iLast)   // if there was the only packet
							{
								ASSERT(iFirst==NULL);
								iLast=NULL;    // set iLast to NULL
							}
							delete first;        // delete first packet
							iCount--;
							}
						else
							{
							//ensure no packets
							ASSERT(iCount==0);
							}
						}
					else
						{
						//attempt to add extra packet
						delete p;
						ASSERT(0);
						}
					}
				}			
			if (iFirst){
				//append at the end
				iPrevious=iLast;
				iLast->iNext=p;
				iLast=p;
			}else{
				//create very first
				iFirst=p;
				iLast=p;
			}
			p->iTask=this;
			p->iSeq=aSeq;
			p->iTime=aTime;
			p->iError=ENoError;
			p->iReceSeq=0;
			p->iNext=NULL;
			iCount++;
			return p;
		}else{
			return NULL;
		}
	}
void CPinQTask::OnSent()
	{
	iSentTime.HomeTime();
	iTimeout.Cancel();	
	SetState(ESent);
	RecvFrom();
	}

void CPinQTask::RecvFrom()
	{
	iError=ENoError;
	SetState(EReceiving);
	iSocket.RecvFrom(iReceBuf,iAddress,0,iStatus);
	ASSERT(!IsActive());
	SetActive();
	iTimeout.Start();
	}

void CPinQTask::NextPing()
	{
	if (Pref().Endless() || Pref().Loop() || (iSeq<Pref().PktCount()))
		{
		if (Pref().Delay())
			{
			ASSERT(!IsActive());
			SetState(EDelay);
			iTimer.After(iStatus,Pref().Delay()*KTimeSecMultiple);
			SetActive();
			}
		else
			{
			SendTo();
			}
		}
	else
		{
		ASSERT(iSeq==Pref().PktCount());
		iSocket.Close();
		SetState(EStopped);
		}	
	}

void CPinQTask::OnRecvd()
	{
	TTime time;
	time.HomeTime();
	iTimeout.Cancel();
	ASSERT(iSeq>0);
	TPacket* packet=LastPacket();
	packet->iReceSeq=(iReceBuf[6]<<8)+iReceBuf[7];
	if (packet->iReceSeq==iSeq)
		packet->iTime=time.MicroSecondsFrom(iSentTime).Int64()/1000;//? convert to ms
	else
		{
		iError=packet->iError=ESeq;
		}
	SetState(EReceived);
	NextPing();
	}

TInt CPinQTask::RunError(TInt aError)
	{
	iLeaveCode=aError; 
	Cancel();
	return aError;
	}

void CPinQTask::ExternalizeL(RWriteStream& aStream) const
	{
	aStream.WriteInt8L(PINQTASKVER);
	aStream.WriteInt32L(iUid.iUid);
	iPref.ExternalizeL(aStream);
	}

void CPinQTask::InternalizeL(RReadStream& aStream)
	{
	TInt ver = aStream.ReadInt8L();
	iUid.iUid = aStream.ReadInt32L();
	iPref.InternalizeL(aStream);
	}

void CPinQTask::OpenL()
	{
	ASSERT (iState == EStopped);

	TInetAddr addr;
	if (addr.Input(iPref.Name()) == KErrNone)
		// server name is already a valid ip address
		OpenL(addr.Address());
	else // need to look up name using dns

		{
		iError=ENoError;
		// Initiate DNS
		// TODO: handle error
		SetState(ELookingUp);
		int e=iResolver.Open(iSocketServ, KAfInet, KProtocolInetUdp);

		if (0==e)
			{
			// DNS request for name resolution
			iResolver.GetByName(iPref.Name(), iNameEntry, iStatus);
			ASSERT(!IsActive());
			SetActive();
			iTimeout.Start();
			}
		else
			{
			//TODO: handle error
			}
		}
	}

void CPinQTask::OpenL(TUint32 aAddr)
	{
	iError=ENoError;			
	SetState(EOpenning);
	iAddress.SetAddress(aAddr);

	TInt e=iSocket.Open(iSocketServ, KAfInet, KSockDatagram, KProtocolInetIcmp);

	if (e==0)
		{
		// Set up address information

		// Initiate socket connection
		//iSocket.Connect(iAddress, iStatus);

		// Start a timeout
		TUint16 id = RThread().SecureId().iId; //TODO: good enough
		iData.Zero();
		iData.Append(0x08); // ICMP type (8 - echo request)
		iData.Append(0x00); // ICMP code (0)
		iData.Append(0x00); // checksum first byte
		iData.Append(0x00); // checksum second byte

		iData.Append(id>>8); // identifier
		iData.Append(id&0x00FF);

		iData.Append(iSeq>>8); // sequence number
		iData.Append(iSeq&0x00FF);

		iData.Append(_L("abcdefghijklmnopqrstuvwabcdefghi")); // data
		
		SendTo();
		}
	else
		{
		//TODO: handle error
		}

	}
// vim: ts=2:sw=2
